package base.patterns.delegate;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import base.check.Check;
import base.patterns.operation.Function2;

public class Delegate2<Result, Param1, Param2> {
	private Slot2<Result, Param1, Param2> objCall = null;
	
	public static <Result, Param1, Param2> boolean isConnected(final Delegate2<Result, Param1, Param2> delegate){
		return delegate.isConnected();
	}

	public static <Result, Param1, Param2> Result call(Delegate2<Result, Param1, Param2> d, Param1 p1, Param2 p2){
		return d.call(p1, p2);
	}
	
	public static <Result, Param1, Param2> void connect(Delegate2<Result, Param1, Param2> delegate, Slot2<Result, Param1, Param2> slot){
		Check.notNull(delegate, "delegate");
		Check.notNull(slot, "slot");
		
		delegate.connect(slot);
	}
	
	public static <Result, Param1, Param2> Slot2<Result, Param1, Param2> disconnect(final Delegate2<Result, Param1, Param2> delegate, final Slot2<Result, Param1, Param2> slot){
		Check.isTrue(isConnected(delegate), Check.raiseException, "Delegate not connected to slot");
		
		return delegate.disconnect();
	}
	
	@SuppressWarnings("unchecked")
	public Result call(final Param1 par1, final Param2 par2){
		Check.isTrue(isConnected(), Check.raiseException, "slot is not connected");
		
		try {
			
			Object res = this.objCall.call(par1, par2);
			return (Result) res;
		} catch (Exception e) {
			
			String error = String.format("Invoke delegate method failed: '%s'. Method '%s'", 
					e.getMessage(), objCall.toString());
			throw new RuntimeException(error , e);
		} 
	}

	
	
	
	private boolean isConnected(){
		return objCall != null;
	}
	
	private void connect(final Slot2<Result, Param1, Param2> slot){
		Check.notNull(slot, "slot is null");
		Check.isFalse(isConnected(), Check.raiseException, "slot is already connected");
		
		this.objCall = slot;
	}
	
	public Slot2<Result, Param1, Param2> disconnect(){
		if ( !isConnected() ){
			return null;
		}
		
		Slot2<Result, Param1, Param2> old = this.objCall; 
		this.objCall = null;
		return old;
	}

	
	@Retention(RetentionPolicy.RUNTIME)
	@Target(ElementType.METHOD)
	public @interface NamedMethod {
		String name();
	}
	
	
	public static class Slot2<Result, Param1, Param2>{
		private Function2<Result, Param1, Param2> callStarategy;
		
		public Slot2(Function2<Result, Param1, Param2> call) {
			Check.notNull(call, "call");
			
			this.callStarategy = call;
		}

		public Result call(Param1 par1, Param2 par2) {
			return this.callStarategy.call(par1, par2);
		}
	}
	
	
}
